from anytime_alpha_beta_extended import INFINITY, TimeException
from math import sqrt, exp
from time import clock

def linearEvaluator(self, state, player,\
                    neighborWeight_ = 1,\
                    diagQuadWeight_ = 5,\
                    threeQuadWeight_ = 7,\
                    squareQuadWeight_ = 9,\
                    areaExp_ = 3,\
                    endTime = None):
    '''
    winner = state.getWinner()
    if winner is None:
        return 0
    elif winner == self.player:
        return 1
    else:
        return -INFINITY
    '''
    if INFINITY == state.winner: return INFINITY
    elif -INFINITY == state.winner: return -INFINITY
    #Format: ( Area, Neighbors, 1Quad, 2Quad, 3Quad, 4Quad, CoGCalc)
    res = linearActions(state, player, endTime)
#        print 'Area\tNeigh.\tCoG\t\t\t\t\t\t2Quad\t3Quad\t4Quad'
#        print str(res[0]) + '\t'+ str(res[1]) + '\t'+ str(res[2]) + '\t\t'+ str(len(res[3])) + '\t'+ str(len(res[4])) + '\t'+ str(len(res[5])) + '\n'
    
#    areaWeight = -100
    neighborWeight = neighborWeight_
    diagQuadWeight = diagQuadWeight_
    threeQuadWeight = threeQuadWeight_
    squareQuadWeight = squareQuadWeight_
#    lonePenaltyWeight = 15
    CoGWeight = 100
    areaExp = areaExp_
    
    #quadBonus = (calcBonus(res[4], res[2]), calcBonus(res[5], res[2]), calcBonus(res[6], res[2]))
    #lonePenalty = calcPenalty(res[3], res[2])
    '''areaWeight       * '''
    '''value = (neighborWeight    * res[1] + \
             diagQuadWeight    * quadBonus[0] + \
             threeQuadWeight   * quadBonus[1] + \
             squareQuadWeight  * quadBonus[2] + \
             CoGWeight         * res[7] + \
             lonePenaltyWeight * lonePenalty)/(float(res[1])**3)'''
    
    state.evaluator = (neighborWeight    * res[1] + \
                       diagQuadWeight    * res[3] + \
                       threeQuadWeight   * res[4] + \
                       squareQuadWeight  * res[5] + \
                       CoGWeight         * res[6] )  \
                       /(float(res[0])**areaExp)
    #lonePenaltyWeight * lonePenalty)/(float(res[0])**3)
    return state.evaluator
'''
TODO:
- search algorithm - anytime behavior and improvements:
    * return result in given time.
    * reordering - evaluator function:
        % linear features implementation: 
            TODO: quads
    * knowledge
- utility function
    * non linear features implementation: BFS.
    * weights
'''

def calcBonus(quadList, centerOfGravity):
    bonus = 0
    for quad in quadList:
        dist = sqrt( (quad[0]-centerOfGravity[0])**2 + (quad[1]-centerOfGravity[1])**2 )
        bonus += exp(-dist)
    return bonus

def calcPenalty(quadList, centerOfGravity):
    penalty = 0
    for quad in quadList:
        dist = sqrt( (quad[0]-centerOfGravity[0])**2 + (quad[1]-centerOfGravity[1])**2 )
        penalty -= exp(dist)
    return penalty

class AreaCalculator():
    
    def __init__(self, player):
        self.minX = INFINITY; self.maxX = -1; self.minY = INFINITY; self.maxY = -1
        self.sumX = 0; self.sumY = 0; self.numOfPieces = 0;
        self.player = player
        self.pieces = []
        
    def update(self, state, i, j):
        if state.board[i][j] == self.player:  
            #Center Of Gravity Calculations
            self.sumX += i; self.sumY+= j; self.numOfPieces += 1
            self.pieces.append((i,j))
            #X Dim 
            if i < self.minX: self.minX = i
            if i > self.maxX: self.maxX = i
            #Y Dim 
            if j < self.minY: self.minY = j
            if j > self.maxY: self.maxY = j
                
    def calc(self):
        #Format: (Area, CoGBonus)
        avgX, avgY = float(self.sumX)/self.numOfPieces, float(self.sumY)/self.numOfPieces
        bonus = 0
        for piece in self.pieces:
            bonus += exp(-sqrt((avgX-piece[0])**2+(avgY-piece[1])**2)) 
            if 0 == (self.maxX-self.minX+1)*(self.maxY-self.minY+1): print '!!!!!!!!!!!!!!!!!!!!!!'
        return ( (self.maxX-self.minX+1)*(self.maxY-self.minY+1), bonus)

class NeigborCounter():
    
    def __init__(self, player):
        self.count = 0
        self.player = player
         
    def update(self, state, i, j):
        if state.board[i][j] == self.player:
            for k in xrange(i-1,i+2):
                for l in xrange (j-1, j+2):
                    if k >= 0 and k < state.size and l >= 0 and l < state.size:
                        if state.board[k][l] == self.player:
                            self.count += 1
                
    def calc(self):
        return self.count

class QuadCounter():
    def __init__(self, player):
        self.lone = 0; self.diagQuad = 0; self.threeQuad = 0; self.squareQuad = 0;
        self.player = player

    def update(self, state, i, j):
        if i < state.size-1 and j < state.size-1:
            count1 = state.board[i][j]     == self.player
            count2 = state.board[i][j+1]   == self.player
            count3 = state.board[i+1][j]   == self.player
            count4 = state.board[i+1][j+1] == self.player
            totalCount =  count1 + count2 + count3 + count4
            if totalCount   == 4: self.squareQuad += 1 #List.append((i+0.5,j+0.5))
            elif totalCount == 3: self.threeQuad += 1 #List.append((i+0.5,j+0.5))
            elif count1 + count4 == 2 or count2 + count3 == 2: self.diagQuad += 1 #List.append((i+0.5,j+0.5))
            elif totalCount == 1: self.lone += 1
            '''
            if count1 == 1:                                                self.loneList.append((i,j))
            elif count2 == 1 and j+1 == state.size-1:                      self.loneList.append((i,j+1))
            elif count3 == 1 and i+1 == state.size-1:                      self.loneList.append((i+1,j))
            elif count4 == 1 and (i+1,j+1) == (state.size-1,state.size-1): self.loneList.append((i+1,j+1))
            '''
                
    def calc(self):
        return (self.lone, self.diagQuad, self.threeQuad, self.squareQuad)
    
def linearActions(state, player, endTime):
    areaCalc = AreaCalculator(player)
    neigborCount = NeigborCounter(player)
    quadCount = QuadCounter(player)
    for i in xrange(0,state.size):
        for j in xrange(0,state.size):
            checkTime(endTime)
            areaCalc.update(state, i, j)
            checkTime(endTime)
            neigborCount.update(state, i, j)
            checkTime(endTime)
            quadCount.update(state, i, j)
    areaRes = areaCalc.calc()        
    quadRes = quadCount.calc()
    #Format: ( Area, Neighbors, 1Quad, 2Quad, 3Quad, 4Quad, CoGCalc)
    return (areaRes[0], neigborCount.calc(), quadRes[0], quadRes[1], quadRes[2], quadRes[3], areaRes[1])

def checkTime(endTime):
    if (endTime == None): return
    if endTime-clock() < 0:
        raise TimeException("Timeout",0)